{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ZloPIuRHn97X"
      },
      "source": [
        "##### Copyright 2019 The TensorFlow Authors. [Licensed under the Apache License, Version 2.0](#scrollTo=Afd8bu4xJOgh)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "cellView": "form",
        "id": "tNgCmfUvJNoF"
      },
      "outputs": [],
      "source": [
        "// #@title Licensed under the Apache License, Version 2.0 (the \"License\"); { display-mode: \"form\" }\n",
        "// Licensed under the Apache License, Version 2.0 (the \"License\");\n",
        "// you may not use this file except in compliance with the License.\n",
        "// You may obtain a copy of the License at\n",
        "//\n",
        "// https://www.apache.org/licenses/LICENSE-2.0\n",
        "//\n",
        "// Unless required by applicable law or agreed to in writing, software\n",
        "// distributed under the License is distributed on an \"AS IS\" BASIS,\n",
        "// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
        "// See the License for the specific language governing permissions and\n",
        "// limitations under the License."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "AlvdCHw5JGyx"
      },
      "source": [
        "<table class=\"tfo-notebook-buttons\" align=\"left\">\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://www.tensorflow.org/swift/tutorials/protocol_oriented_generics\"><img src=\"https://www.tensorflow.org/images/tf_logo_32px.png\" />View on TensorFlow.org</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://colab.research.google.com/github/tensorflow/swift/blob/master/docs/site/tutorials/protocol_oriented_generics.ipynb\"><img src=\"https://www.tensorflow.org/images/colab_logo_32px.png\" />Run in Google Colab</a>\n",
        "  </td>\n",
        "  <td>\n",
        "    <a target=\"_blank\" href=\"https://github.com/tensorflow/swift/blob/master/docs/site/tutorials/protocol_oriented_generics.ipynb\"><img src=\"https://www.tensorflow.org/images/GitHub-Mark-32px.png\" />View source on GitHub</a>\n",
        "  </td>\n",
        "</table>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c_1u7JSBMx3x"
      },
      "source": [
        "# Protocol-oriented programming & generics\n",
        "\n",
        "This tutorial will go over protocol-oriented programming, and different examples of how they can be used with generics in day-to-day examples."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LP0gMw56TlvH"
      },
      "source": [
        "## Protocols\n",
        "\n",
        "Inheritance is a powerful way to organize code in programming languages that allows you to share code between multiple components of the program.\n",
        "\n",
        "In Swift, there are different ways to express inheritance. You may already be familiar with one of those ways, from other languages: class inheritance. However, Swift has another way: protocols.\n",
        "\n",
        "In this tutorial, we will explore protocols - an alternative to subclassing that allows you to achieve similar goals through different tradeoffs. In Swift, protocols contain multiple abstract members. Classes, structs and enums can conform to multiple protocols and the conformance relationship can be established retroactively. All that enables some designs that aren't easily expressible in Swift using subclassing. We will walk through the idioms that support the use of protocols (extensions and protocol constraints), as well as the limitations of protocols.\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "5AIIH5Q59b41"
      },
      "source": [
        "## Swift 💖's value types!\n",
        "\n",
        "In addition to classes which have reference semantics, Swift supports enums and structs that are passed by value. Enums and structs support many features provided by classes. Let's take a look!\n",
        "\n",
        "Firstly, let's look at how enums are similar to classes:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "zCN0Uc0w-gng"
      },
      "outputs": [],
      "source": [
        "enum Color: String {\n",
        "    case red = \"red\"\n",
        "    case green = \"green\"\n",
        "    case blue = \"blue\"\n",
        "    // A computed property. Note that enums cannot contain stored properties.\n",
        "    var hint: String {\n",
        "        switch self {\n",
        "            case .red:\n",
        "                return \"Roses are this color.\"\n",
        "            case .green:\n",
        "                return \"Grass is this color.\"\n",
        "            case .blue:\n",
        "                return \"The ocean is this color.\"\n",
        "        }\n",
        "    }\n",
        "    \n",
        "    // An initializer like for classes.\n",
        "    init?(color: String) {\n",
        "        switch color {\n",
        "        case \"red\":\n",
        "            self = .red\n",
        "        case \"green\":\n",
        "            self = .green\n",
        "        case \"blue\":\n",
        "            self = .blue\n",
        "        default:\n",
        "            return nil\n",
        "        }\n",
        "    }\n",
        "}\n",
        "\n",
        "// Can extend the enum as well!\n",
        "extension Color {\n",
        "    // A function.\n",
        "    func hintFunc() -> String {\n",
        "        return self.hint\n",
        "    }\n",
        "}\n",
        "\n",
        "let c = Color.red\n",
        "print(\"Give me a hint for c: \\(c.hintFunc())\")\n",
        "\n",
        "let invalidColor = Color(color: \"orange\")\n",
        "print(\"is invalidColor nil: \\(invalidColor == nil)\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ueiGy9gCgypk"
      },
      "source": [
        "Now, let's look at structs. Notice that we cannot inherit structs, but instead can use protocols:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "ns4qCE1pg3uJ"
      },
      "outputs": [],
      "source": [
        "struct FastCar {\n",
        "    // Can have variables and constants as stored properties.\n",
        "    var color: Color\n",
        "    let horsePower: Int\n",
        "    // Can have computed properties.\n",
        "    var watts: Float {\n",
        "       return Float(horsePower) * 745.7\n",
        "    }\n",
        "    // Can have lazy variables like in classes!\n",
        "    lazy var titleCaseColorString: String = {\n",
        "        let colorString = color.rawValue\n",
        "        return colorString.prefix(1).uppercased() + \n",
        "               colorString.lowercased().dropFirst()\n",
        "    }()\n",
        "    // A function.\n",
        "    func description() -> String {\n",
        "        return \"This is a \\(color) car with \\(horsePower) horse power!\"\n",
        "    }\n",
        "    // Can create a variety of initializers.\n",
        "    init(color: Color, horsePower: Int) {\n",
        "        self.color = color\n",
        "        self.horsePower = horsePower\n",
        "    }\n",
        "    // Can define extra initializers other than the default one.\n",
        "    init?(color: String, horsePower: Int) {\n",
        "        guard let enumColor = Color(color: color) else {\n",
        "            return nil\n",
        "        }\n",
        "        self.color = enumColor\n",
        "        self.horsePower = horsePower\n",
        "    }\n",
        "}\n",
        "\n",
        "var car = FastCar(color: .red, horsePower: 250)\n",
        "print(car.description())\n",
        "print(\"Horse power in watts: \\(car.watts)\")\n",
        "print(car.titleCaseColorString)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "1Hw8bpQIlaWT"
      },
      "source": [
        "Finally, let's see how they are pass by value types unlike classes:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "C67qzGBVlhdo"
      },
      "outputs": [],
      "source": [
        "// Notice we have no problem modifying a constant class with \n",
        "// variable properties.\n",
        "class A {\n",
        "  var a = \"a\"\n",
        "}\n",
        "\n",
        "func foo(_ a: A) {\n",
        "  a.a = \"foo\"\n",
        "}\n",
        "let a = A()\n",
        "print(a.a)\n",
        "foo(a)\n",
        "print(a.a)\n",
        "\n",
        "/* \n",
        "Uncomment the following code to see how an error is thrown.\n",
        "Structs are implicitly passed by value, so we cannot modify it.\n",
        "> \"error: cannot assign to property: 'car' is a 'let' constant\"\n",
        "*/\n",
        "\n",
        "// func modify(car: FastCar, toColor color: Color) -> Void {\n",
        "//   car.color = color\n",
        "// }\n",
        "\n",
        "// car = FastCar(color: .red, horsePower: 250)\n",
        "// print(car.description())\n",
        "// modify(car: &car, toColor: .blue)\n",
        "// print(car.description())\n"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "0BNxC5RyoKM7"
      },
      "source": [
        "## Let's use protocols\n",
        "\n",
        "Let's start by creating protocols for different cars:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "3ZSm1uTWoJ0h"
      },
      "outputs": [],
      "source": [
        "protocol Car {\n",
        "    var color: Color { get set }\n",
        "    var price: Int { get }\n",
        "    func turnOn()\n",
        "    mutating func drive()\n",
        "}\n",
        "\n",
        "protocol Electric {\n",
        "    mutating func recharge()\n",
        "    // percentage of the battery level, 0-100%.\n",
        "    var batteryLevel: Int { get set }\n",
        "}\n",
        "\n",
        "protocol Gas {\n",
        "    mutating func refill()\n",
        "    // # of liters the car is holding, varies b/w models.\n",
        "    var gasLevelLiters: Int { get set }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "aV_F6MyLps3h"
      },
      "source": [
        "In an object-oriented world (with no multiple inheritance), you may have made `Electric` and `Gas` abstract classes then used class inheritance to make both inherit from `Car`, and then have a specific car model be a base class. However, here both are completely separate protocols with **zero** coupling! This makes the entire system more flexible in how you design it.\n",
        "\n",
        "Let's define a Tesla:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "LkGRtwz3psrP"
      },
      "outputs": [],
      "source": [
        "struct TeslaModelS: Car, Electric {\n",
        "    var color: Color // Needs to be a var since `Car` has a getter and setter.\n",
        "    let price: Int\n",
        "    var batteryLevel: Int\n",
        "    \n",
        "    func turnOn() {\n",
        "        print(\"Starting all systems!\")\n",
        "    }\n",
        "\n",
        "    mutating func drive() {\n",
        "        print(\"Self driving engaged!\")\n",
        "        batteryLevel -= 8\n",
        "    }\n",
        "\n",
        "    mutating func recharge() {\n",
        "        print(\"Recharging the battery...\")\n",
        "        batteryLevel = 100\n",
        "    }\n",
        "}\n",
        "\n",
        "var tesla = TeslaModelS(color: .red, price: 110000, batteryLevel: 100)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "K5KB0IGLrkcm"
      },
      "source": [
        "This specifies a new struct `TeslaModelS` that conforms to both protocols `Car` and `Electric`.\n",
        "\n",
        "Now let’s define a gas powered car:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "GPLKFUAOrMp-"
      },
      "outputs": [],
      "source": [
        "struct Mustang: Car, Gas{\n",
        "    var color: Color\n",
        "    let price: Int\n",
        "    var gasLevelLiters: Int\n",
        "    \n",
        "    func turnOn() {\n",
        "        print(\"Starting all systems!\")\n",
        "    }\n",
        "    \n",
        "    mutating func drive() {\n",
        "        print(\"Time to drive!\")\n",
        "        gasLevelLiters -= 1\n",
        "    }\n",
        "    \n",
        "    mutating func refill() {\n",
        "        print(\"Filling the tank...\")\n",
        "        gasLevelLiters = 25\n",
        "    }\n",
        "}\n",
        "\n",
        "var mustang = Mustang(color: .red, price: 30000, gasLevelLiters: 25)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jrbCRkglsi_d"
      },
      "source": [
        "### Extend protocols with default behaviors\n",
        "\n",
        "What you can notice from the examples is that we have some redundancy. Every time we recharge an electric car, we need to set the battery percentage level to 100. Since all electric cars have a max capacity of 100%, but gas cars vary between gas tank capacity, we can default the level to 100 for electric cars.\n",
        "\n",
        "This is where extensions in Swift can come in handy:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "NiHUJxXMtzSg"
      },
      "outputs": [],
      "source": [
        "extension Electric {\n",
        "    mutating func recharge() {\n",
        "        print(\"Recharging the battery...\")\n",
        "        batteryLevel = 100\n",
        "    }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "v8QbTb9NxWqI"
      },
      "source": [
        "So now, any new electric car we create will set the battery to 100 when we recharge it. Thus, we have just been able to decorate classes, structs, and enums with unique and default behavior."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "VIXakqxtvkp_"
      },
      "source": [
        "![Protocol Comic](https://koenig-media.raywenderlich.com/uploads/2015/06/protocols-extend.png)\n",
        "\n",
        "Thanks to [Ray Wenderlich](https://www.raywenderlich.com/814-introducing-protocol-oriented-programming-in-swift-3) for the comic!"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "MAo8n3zUR8Q9"
      },
      "source": [
        "However, one thing to watch out for is the following. In our first implementation, we define `foo()` as a default implementation on `A`, but not make it required in the protocol. So when we call `a.foo()`, we get \"`A default`\" printed."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "koP20_C9R7ps"
      },
      "outputs": [],
      "source": [
        "protocol Default {}\n",
        "\n",
        "extension Default {\n",
        "    func foo() { print(\"A default\")}\n",
        "}\n",
        "\n",
        "struct DefaultStruct: Default {\n",
        "    func foo() {\n",
        "        print(\"Inst\")\n",
        "    }\n",
        "}\n",
        "\n",
        "let a: Default = DefaultStruct()\n",
        "a.foo()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uSE5IWF_Sdet"
      },
      "source": [
        "However, if we make `foo()` required on `A`, we get \"`Inst`\":"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "DJ8jstIWSoUP"
      },
      "outputs": [],
      "source": [
        "protocol Default {\n",
        "    func foo()\n",
        "}\n",
        "\n",
        "extension Default {\n",
        "    func foo() { \n",
        "        print(\"A default\")\n",
        "    }\n",
        "}\n",
        "\n",
        "struct DefaultStruct: Default {\n",
        "    func foo() {\n",
        "        print(\"Inst\")\n",
        "    }\n",
        "}\n",
        "\n",
        "let a: Default = DefaultStruct()\n",
        "a.foo()"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "XC1juXPzZV8Q"
      },
      "source": [
        "This occurs due to a difference between static dispatch in the first example and static dispatch in the second on protocols in Swift. For more info, refer to this [Medium post](https://medium.com/@PavloShadov/https-medium-com-pavloshadov-swift-protocols-magic-of-dynamic-static-methods-dispatches-dfe0e0c85509)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "HzB0XC0wwMLD"
      },
      "source": [
        "### Overriding default behavior\n",
        "\n",
        "However, if we want to, we can still override the default behavior. One important thing to note is that this [doesn’t support dynamic dispatch](https://stackoverflow.com/questions/44703205/swift-protocol-extension-method-is-called-instead-of-method-implemented-in-subcl).\n",
        "\n",
        "Let’s say we have an older version of an electric car, so the battery health has been reduced to 90%:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "CaIZhcsVyBKz"
      },
      "outputs": [],
      "source": [
        "struct OldElectric: Car, Electric {\n",
        "    var color: Color // Needs to be a var since `Car` has a getter and setter.\n",
        "    let price: Int\n",
        "    var batteryLevel: Int\n",
        "    \n",
        "    func turnOn() {\n",
        "        print(\"Starting all systems!\")\n",
        "    }\n",
        "    \n",
        "    mutating func drive() {\n",
        "        print(\"Self driving engaged!\")\n",
        "        batteryLevel -= 8\n",
        "    }\n",
        "    \n",
        "    mutating func reCharge() {\n",
        "        print(\"Recharging the battery...\")\n",
        "        batteryLevel = 90\n",
        "    }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "c_Xmw5cDy_rZ"
      },
      "source": [
        "## Standard library uses of protocols\n",
        "\n",
        "Now that we have an idea how protocols in Swift work, let's go through some typical examples of using the standard library protocols.\n",
        "\n",
        "### Extend the standard library\n",
        "Let's see how we can add additional functionality to types that exist in Swift already. Since types in Swift aren't built in, but are part of the standard library as structs, this is easy to do.\n",
        "\n",
        "Let's try and do binary search on an array of elements, while also making sure to check that the array is sorted:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "-Jfn3P3P1RDt"
      },
      "outputs": [],
      "source": [
        "extension Collection where Element: Comparable {\n",
        "    // Verify that a `Collection` is sorted.\n",
        "    func isSorted(_ order: (Element, Element) -> Bool) -> Bool {\n",
        "        var i = index(startIndex, offsetBy: 1)\n",
        "        \n",
        "        while i < endIndex {\n",
        "            // The longer way of calling a binary function like `<(_:_:)`, \n",
        "            // `<=(_:_:)`, `==(_:_:)`, etc.\n",
        "            guard order(self[index(i, offsetBy: -1)], self[i]) else {\n",
        "                return false\n",
        "            }\n",
        "            i = index(after: i)\n",
        "        }\n",
        "        return true\n",
        "    }\n",
        "    \n",
        "    // Perform binary search on a `Collection`, verifying it is sorted.\n",
        "    func binarySearch(_ element: Element) -> Index? {\n",
        "        guard self.isSorted(<=) else {\n",
        "            return nil\n",
        "        }\n",
        "        \n",
        "        var low = startIndex\n",
        "        var high = endIndex\n",
        "        \n",
        "        while low <= high {\n",
        "            let mid = index(low, offsetBy: distance(from: low, to: high)/2)\n",
        "\n",
        "            if self[mid] == element {\n",
        "                return mid\n",
        "            } else if self[mid] < element {\n",
        "                low = index(after: mid)\n",
        "            } else {\n",
        "                high = index(mid, offsetBy: -1)\n",
        "            }\n",
        "        }\n",
        "        \n",
        "        return nil\n",
        "    }\n",
        "}\n",
        "\n",
        "print([2, 2, 5, 7, 11, 13, 17].binarySearch(5)!)\n",
        "print([\"a\", \"b\", \"c\", \"d\"].binarySearch(\"b\")!)\n",
        "print([1.1, 2.2, 3.3, 4.4, 5.5].binarySearch(3.3)!)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "CIh7qyFVqlaH"
      },
      "source": [
        "We do this by extending the [`Collection`](https://developer.apple.com/documentation/swift/collection) protocol which defines _\"a sequence whose elements can be traversed multiple times, nondestructively, and accessed by an indexed subscript.\"_ Since arrays can be indexed using the square bracket notation, this is the protocol we want to extend.\n",
        "\n",
        "Similarly, we only want to add this utility function to arrays whose elements can be compared. This is the reason why we have `where Element: Comparable`. \n",
        "\n",
        "The `where` clause is a part of Swift's type system, which we will cover soon, but in short lets us add additional requirements to the extension we are writing, such as to require the type to implement a protocol, to require two types to be the same, or to require a class to have a particular superclass.\n",
        "\n",
        "[`Element`](https://developer.apple.com/documentation/swift/sequence/2908099-element) is the associated type of the elements in a `Collection`-conforming type. `Element` is defined within the [`Sequence`](https://developer.apple.com/documentation/swift/sequence) protocol, but since `Collection` inherits from `Sequence`, it inherits the `Element` associated type.\n",
        "\n",
        "[`Comparable`](https://developer.apple.com/documentation/swift/comparable) is a protocol that defines _\"a type that can be compared using the relational operators `<`, `<=`, `>=`, and `>`.\"_. Since we are performing binary search on a sorted `Collection`, this of course has to be true or else we don't know whether to recurse/iterate left or right in the binary search.\n",
        "\n",
        "As a side note about the implementation,  for more info on the `index(_:offsetBy:)` function that was used, refer to the following [documentation](https://developer.apple.com/documentation/swift/string/1786175-index)."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "TmqFx2layKs7"
      },
      "source": [
        "## Generics + protocols = 💥\n",
        "Generics and protocols can be a powerful tool if used correctly to avoid duplicate code.\n",
        "\n",
        "Firstly, look over another tutorial, [A Swift Tour](https://colab.research.google.com/github/tensorflow/swift/blob/master/docs/site/tutorials/a_swift_tour.ipynb), which briefly covers generics at the end of the Colab book.\n",
        "\n",
        "Assuming you have a general idea about generics, let's quickly take a look at some advanced uses.\n",
        "\n",
        "When a single type has multiple requirements such as a type conforming to several protocols, you have several options at your disposal:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "HIjkHLGtz268"
      },
      "outputs": [],
      "source": [
        "typealias ComparableReal = Comparable & FloatingPoint\n",
        "\n",
        "func foo1<T: ComparableReal>(a: T, b: T) -> Bool {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "func foo2<T: Comparable & FloatingPoint>(a: T, b: T) -> Bool {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "func foo3<T>(a: T, b: T) -> Bool where T: ComparableReal {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "func foo4<T>(a: T, b: T) -> Bool where T: Comparable & FloatingPoint {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "func foo5<T: FloatingPoint>(a: T, b: T) -> Bool where T: Comparable {\n",
        "    return a > b\n",
        "}\n",
        "\n",
        "print(foo1(a: 1, b: 2))\n",
        "print(foo2(a: 1, b: 2))\n",
        "print(foo3(a: 1, b: 2))\n",
        "print(foo4(a: 1, b: 2))\n",
        "print(foo5(a: 1, b: 2))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "DMCioS9Dz5Fh"
      },
      "source": [
        "Notice the use of `typealias` at the top. This adds a named alias of an existing type into your program. After a type alias is declared, the aliased name can be used instead of the existing type everywhere in your program. Type aliases do not create new types; they simply allow a name to refer to an existing type.\n",
        "\n",
        "Now, let's see how we can use protocols and generics together.\n",
        "\n",
        "Let's imagine we are a computer store with the following requirements on any laptop we sell for determining how we organize them in the back of the store:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "oKvieIsw2YJW"
      },
      "outputs": [],
      "source": [
        "enum Box {\n",
        "    case small\n",
        "    case medium\n",
        "    case large\n",
        "}\n",
        "\n",
        "enum Mass {\n",
        "    case light\n",
        "    case medium\n",
        "    case heavy\n",
        "}\n",
        "\n",
        "// Note: `CustomStringConvertible` protocol lets us pretty-print a `Laptop`.\n",
        "struct Laptop: CustomStringConvertible {\n",
        "    var name: String\n",
        "    var box: Box\n",
        "    var mass: Mass\n",
        "    \n",
        "    var description: String {\n",
        "        return \"(\\(self.name) \\(self.box) \\(self.mass))\"\n",
        "    }\n",
        "}"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "ey519vu04FgG"
      },
      "source": [
        "However, we have a new requirement of grouping our `Laptop`s by mass since the shelves have weight restrictions."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "tVRuqx_q4jQ9"
      },
      "outputs": [],
      "source": [
        "func filtering(_ laptops: [Laptop], by mass: Mass) -> [Laptop] {\n",
        "    return laptops.filter { $0.mass == mass }\n",
        "}\n",
        "\n",
        "let laptops: [Laptop] = [\n",
        "    Laptop(name: \"a\", box: .small, mass: .light),\n",
        "    Laptop(name: \"b\", box: .large, mass: .medium),\n",
        "    Laptop(name: \"c\", box: .medium, mass: .heavy),\n",
        "    Laptop(name: \"d\", box: .large, mass: .light)\n",
        "]\n",
        "\n",
        "let filteredLaptops = filtering(laptops, by: .light)\n",
        "print(filteredLaptops)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "Lw_W5zW17UMc"
      },
      "source": [
        "However, what if we wanted to filter by something other than `Mass`? \n",
        "\n",
        "One option is to do the following:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "2qDVD9Yl8POQ"
      },
      "outputs": [],
      "source": [
        "// Define a protocol which will act as our comparator.\n",
        "protocol DeviceFilterPredicate {\n",
        "    associatedtype Device\n",
        "    func shouldKeep(_ item: Device) -> Bool\n",
        "}\n",
        "\n",
        "// Define the structs we will use for passing into our filtering function.\n",
        "struct BoxFilter: DeviceFilterPredicate {\n",
        "    typealias Device = Laptop\n",
        "    var box: Box \n",
        "    \n",
        "    func shouldKeep(_ item: Laptop) -> Bool {\n",
        "        return item.box == box\n",
        "    }\n",
        "}\n",
        "\n",
        "struct MassFilter: DeviceFilterPredicate {\n",
        "    typealias Device = Laptop  \n",
        "    var mass: Mass\n",
        "    \n",
        "    func shouldKeep(_ item: Laptop) -> Bool {\n",
        "        return item.mass == mass\n",
        "    }\n",
        "}\n",
        "\n",
        "// Make sure our filter conforms to `DeviceFilterPredicate` and that we are \n",
        "// filtering `Laptop`s.\n",
        "func filtering<F: DeviceFilterPredicate>(\n",
        "    _ laptops: [Laptop], \n",
        "    by filter: F\n",
        ") -> [Laptop] where Laptop == F.Device {\n",
        "    return laptops.filter { filter.shouldKeep($0) }\n",
        "}\n",
        "\n",
        "// Let's test the function out!\n",
        "print(filtering(laptops, by: BoxFilter(box: .large)))\n",
        "print(filtering(laptops, by: MassFilter(mass: .heavy)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "yFx_6y0-CRHc"
      },
      "source": [
        "Awesome! Now we are able to filter based on any laptop constraint. However, we are only able to filter `Laptop`s. \n",
        "\n",
        "What about being able to filter anything that is in a box and has mass? Maybe this warehouse of laptops will also be used for servers which have a different customer base:"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {
        "id": "EzhE_K-eCvYt"
      },
      "outputs": [],
      "source": [
        "// Define 2 new protocols so we can filter anything in a box and which has mass.\n",
        "protocol Weighable {\n",
        "    var mass: Mass { get }\n",
        "}\n",
        "\n",
        "protocol Boxed {\n",
        "    var box: Box { get }\n",
        "}\n",
        "\n",
        "// Define the new Laptop and Server struct which have mass and a box.\n",
        "struct Laptop: CustomStringConvertible, Boxed, Weighable {\n",
        "    var name: String\n",
        "    var box: Box\n",
        "    var mass: Mass\n",
        "    \n",
        "    var description: String {\n",
        "        return \"(\\(self.name) \\(self.box) \\(self.mass))\"\n",
        "    }\n",
        "}\n",
        "\n",
        "struct Server: CustomStringConvertible, Boxed, Weighable {\n",
        "    var isWorking: Bool\n",
        "    var name: String\n",
        "    let box: Box\n",
        "    let mass: Mass\n",
        "\n",
        "    var description: String {\n",
        "        if isWorking {\n",
        "            return \"(working \\(self.name) \\(self.box) \\(self.mass))\"\n",
        "        } else {\n",
        "            return \"(notWorking \\(self.name) \\(self.box) \\(self.mass))\"\n",
        "        }\n",
        "    }\n",
        "}\n",
        "\n",
        "// Define the structs we will use for passing into our filtering function.\n",
        "struct BoxFilter<T: Boxed>: DeviceFilterPredicate {\n",
        "    var box: Box \n",
        "  \n",
        "    func shouldKeep(_ item: T) -> Bool {\n",
        "        return item.box == box\n",
        "    }\n",
        "}\n",
        "\n",
        "struct MassFilter<T: Weighable>: DeviceFilterPredicate {\n",
        "    var mass: Mass\n",
        "    \n",
        "    func shouldKeep(_ item: T) -> Bool {\n",
        "        return item.mass == mass\n",
        "    }\n",
        "}\n",
        "\n",
        "// Define the new filter function.\n",
        "func filtering<F: DeviceFilterPredicate, T>(\n",
        "    _ elements: [T], \n",
        "    by filter: F\n",
        ") -> [T] where T == F.Device {\n",
        "    return elements.filter { filter.shouldKeep($0) }\n",
        "}\n",
        "\n",
        "\n",
        "// Let's test the function out!\n",
        "let servers = [\n",
        "    Server(isWorking: true, name: \"serverA\", box: .small, mass: .heavy),\n",
        "    Server(isWorking: false, name: \"serverB\", box: .medium, mass: .medium),\n",
        "    Server(isWorking: true, name: \"serverC\", box: .large, mass: .light),\n",
        "    Server(isWorking: false, name: \"serverD\", box: .medium, mass: .light),\n",
        "    Server(isWorking: true, name: \"serverE\", box: .small, mass: .heavy)\n",
        "]\n",
        "\n",
        "let products = [\n",
        "    Laptop(name: \"a\", box: .small, mass: .light),\n",
        "    Laptop(name: \"b\", box: .large, mass: .medium),\n",
        "    Laptop(name: \"c\", box: .medium, mass: .heavy),\n",
        "    Laptop(name: \"d\", box: .large, mass: .light)\n",
        "]\n",
        "\n",
        "print(filtering(servers, by: BoxFilter(box: .small)))\n",
        "print(filtering(servers, by: MassFilter(mass: .medium)))\n",
        "\n",
        "print(filtering(products, by: BoxFilter(box: .small)))\n",
        "print(filtering(products, by: MassFilter(mass: .medium)))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "-UrRDSaFNCRg"
      },
      "source": [
        "We have now been able to filter an array by not only any property of a specific `struct`, but also be able to filter any struct which has that property!"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "6g3pPZIuMvPu"
      },
      "source": [
        "# Tips for good API design\n",
        "***This section was taken from the [WWDC 2019: Modern Swift API Design](https://developer.apple.com/videos/play/wwdc2019/415/) talk.***\n",
        "\n",
        "Now that you understand how protocols behave, it's best to go over when you should use protocols. As powerful as protocols can be, it's not always the best idea to dive in and immediately start with protocols.\n",
        "\n",
        "* Start with concrete use cases:\n",
        "    * First explore the use case with concrete types and understand what code it is you want to share and find is being repeated. Then, factor that shared code out with generics. It might mean to create new protocols. Discover a need for generic code.\n",
        "* Consider composing new protocols from existing protocols defined in the standard library. Refer to the following [Apple documentation](https://developer.apple.com/documentation/swift/adopting_common_protocols) for a good example of this.\n",
        "* Instead of a generic protocol, consider defining a generic type instead.\n",
        "\n",
        "## Example: defining a custom vector type\n",
        "Let's say we want to define a `GeometricVector` protocol on floating-point numbers to use in some geometry app we are making which defines 3 important vector operations:\n",
        "\n",
        "```swift\n",
        "protocol GeometricVector {\n",
        "    associatedtype Scalar: FloatingPoint\n",
        "    static func dot(_ a: Self, _ b: Self) -> Scalar\n",
        "    var length: Scalar { get }\n",
        "    func distance(to other: Self) -> Scalar\n",
        "}\n",
        "```\n",
        "\n",
        "Let's say we want to store the dimensions of the vector, which the `SIMD` protocol can help us with, so we will make our new type refine the `SIMD` protocol. `SIMD` vectors can be thought of as fixed size vectors that are very fast when you use them to perform vector operations:\n",
        "\n",
        "```swift\n",
        "protocol GeometricVector: SIMD {\n",
        "    associatedtype Scalar: FloatingPoint\n",
        "    static func dot(_ a: Self, _ b: Self) -> Scalar\n",
        "    var length: Scalar { get }\n",
        "    func distance(to other: Self) -> Scalar\n",
        "}\n",
        "```\n",
        "\n",
        "Now, let us define the default implementations of the operations above:\n",
        "\n",
        "```swift\n",
        "extension GeometricVector {\n",
        "    static func dot(_ a: Self, _ b: Self) -> Scalar {\n",
        "        (a * b).sum()\n",
        "    }\n",
        "\n",
        "    var length: Scalar {\n",
        "        Self.dot(self, self).squareRoot()\n",
        "    }\n",
        "\n",
        "    func distance(to other: Self) -> Scalar {\n",
        "        (self - other).length\n",
        "    }\n",
        "}\n",
        "```\n",
        "\n",
        "And then we need to add a conformance to each of the types we want to add these abilities:\n",
        "```swift\n",
        "extension SIMD2: GeometricVector where Scalar: FloatingPoint { }\n",
        "extension SIMD3: GeometricVector where Scalar: FloatingPoint { }\n",
        "extension SIMD4: GeometricVector where Scalar: FloatingPoint { }\n",
        "extension SIMD8: GeometricVector where Scalar: FloatingPoint { }\n",
        "extension SIMD16: GeometricVector where Scalar: FloatingPoint { }\n",
        "extension SIMD32: GeometricVector where Scalar: FloatingPoint { }\n",
        "extension SIMD64: GeometricVector where Scalar: FloatingPoint { }\n",
        "```\n",
        "\n",
        "This three-step process of defining the protocol, giving it a default implementation, and then adding a conformance to multiple types is fairly repetitive.\n",
        "\n",
        "## Was the protocol necessary?\n",
        "The fact that none of the `SIMD` types have unique implementations is a warning sign. So in this case, the protocol isn't really giving us anything.\n",
        "\n",
        "## Defining it in an extension of `SIMD`\n",
        "If we write the 3 operators in an extension of the `SIMD` protocol, this can solve the problem more succinctly:\n",
        "\n",
        "```swift\n",
        "extension SIMD where Scalar: FloatingPoint {\n",
        "    static func dot(_ a: Self, _ b: Self) -> Scalar {\n",
        "        (a * b).sum()\n",
        "    }\n",
        "\n",
        "    var length: Scalar {\n",
        "        Self.dot(self, self).squareRoot()\n",
        "    }\n",
        "\n",
        "    func distance(to other: Self) -> Scalar {\n",
        "        (self - other).length\n",
        "    }\n",
        "}\n",
        "```\n",
        "\n",
        "Using less lines of code, we added all the default implementations to all the types of `SIMD`.\n",
        "\n",
        "Sometimes you may be tempted to create this hierarchy of types, but remember that it isn't always necessary. This also means the binary size of your compiled program will be smaller, and your code will be faster to compile. \n",
        "\n",
        "However, this extension approach is great for when you have a few number of methods you want to add. However, it does hit a scalability issue when you are designing a larger API.\n",
        "\n",
        "## Is-a? Has-a?\n",
        "Earlier we said `GeometricVector` would refine `SIMD`. But is this a is-a relationship? The problem is that `SIMD` defines operations which lets us add a scalar 1 to a vector, but it doesn't make sense to define such an operation in the context of geometry. \n",
        "\n",
        "So, maybe a has-a relationship would be better by wrapping `SIMD` in a new generic type that can handle any floating point number:\n",
        "\n",
        "```swift\n",
        "// NOTE: `Storage` is the underlying type that is storing the values, \n",
        "// just like in a `SIMD` vector.\n",
        "struct GeometricVector<Storage: SIMD> where Storage.Scalar: FloatingPoint {\n",
        "    typealias Scalar = Storage.Scalar\n",
        "    var value: Storage\n",
        "    init(_ value: Storage) { self.value = value }\n",
        "}\n",
        "```\n",
        "\n",
        "We can then be careful and only define the operations that make sense only in the context of geometry:\n",
        "\n",
        "```swift\n",
        "extension GeometricVector {\n",
        "    static func + (a: Self, b: Self) -> Self {\n",
        "        Self(a.value + b.value)\n",
        "    }\n",
        "\n",
        "    static func - (a: Self, b: Self) -> Self {\n",
        "        Self(a.value - b.value)\n",
        "    }\n",
        "    static func * (a: Self, b: Scalar) -> Self {\n",
        "        Self(a.value * b)\n",
        "    }\n",
        "}\n",
        "```\n",
        "\n",
        "And we can still use generic extensions to get the 3 previous operators we wanted to implement which look almost the exact same as before:\n",
        "\n",
        "```swift\n",
        "extension GeometricVector {\n",
        "    static func dot(_ a: Self, _ b: Self) -> Scalar {\n",
        "        (a.value * b.value).sum()\n",
        "    }\n",
        "\n",
        "    var length: Scalar {\n",
        "        Self.dot(self, self).squareRoot()\n",
        "    }\n",
        "\n",
        "    func distance(to other: Self) -> Scalar {\n",
        "        (self - other).length\n",
        "    }\n",
        "}\n",
        "```\n",
        "\n",
        "Overall, we have been able to refine the behavior of our three operations to a type by simply using a struct. With protocols, we faced the issue of writing repetitive conformances to all the `SIMD` vectors, and also weren't able to prevent certain operators like `Scalar + Vector` from being available (which in this case we didn't want). As such, remember that protocols are not a be-all and end-all solution. But sometimes more traditional solutions can prove to be more powerful."
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "UVLumcugImNx"
      },
      "source": [
        "# More protocol-oriented programming resources\n",
        "Here are additional resources on the topics discussed:\n",
        "\n",
        "* [WWDC 2015: Protocol-Oriented Programming in Swift](https://developer.apple.com/videos/play/wwdc2015/408/): this was presented using Swift 2, so a lot has changed since then (e.g. name of the protocols they used in the presentation) but this is still a good resource for the theory and uses behind it.\n",
        "* [Introducing Protocol-Oriented Programming in Swift 3](https://www.raywenderlich.com/814-introducing-protocol-oriented-programming-in-swift-3): this was written in Swift 3, so some of the code may need to be modified in order to have it compile successfully, but it is another great resource.\n",
        "* [WWDC 2019: Modern Swift API Design](https://developer.apple.com/videos/play/wwdc2019/415/): goes over the differences between value and reference types, a use case of when protocols can prove to be the worse choice in API design (same as the \"Tips for Good API Design\" section above), key path member lookup, and property wrappers.\n",
        "* [Generics](https://docs.swift.org/swift-book/LanguageGuide/Generics.html): Swift's own documentation for Swift 5 all about generics."
      ]
    }
  ],
  "metadata": {
    "colab": {
      "collapsed_sections": [],
      "name": "protocol_oriented_generics.ipynb",
      "toc_visible": true
    },
    "kernelspec": {
      "display_name": "Swift",
      "name": "swift"
    }
  },
  "nbformat": 4,
  "nbformat_minor": 0
}
